﻿
#pragma once

#include <QtCharts/qchart.h>
#include <QtCharts/qlineseries.h>
#include <QtCore/qsettings.h>
#include <QtCore/qstring.h>
#include <QtGui/qevent.h>
#include <QtGui/qvalidator.h>
#include <QtWidgets/qmainwindow.h>
#include <QtWidgets/qmessagebox.h>
#include <QtWidgets/qpushbutton.h>
#include <QtWidgets/qscrollbar.h>

#include <cassert>
#include <cuchar>
#include <memory>

#include "lipid/lipid_loop.hpp"
#include "ui_HeatSimMain.h"


class SettingsScope {
   private:
    QSettings &_settings;
    QStringView _group_name;

   public:
    SettingsScope(QSettings &settings, QStringView group_name) :
        _settings(settings), _group_name(group_name) {
        //
        _settings.beginGroup(_group_name);
    }

    ~SettingsScope() {
        _settings.endGroup();
    }

    QSettings &get_settings() {
        return _settings;
    }

    __declspec(property(get = get_settings)) QSettings &settings;
};

struct PidParamPack {
   private:
    float _kp = 0.0f;
    float _ki = 0.0f;
    float _kd = 0.0f;
    float _kf = 1.0f;  // 用于反馈环的增益系数

   public:
    float kp() const {
        return _kp;
    }

    float ki() const {
        return _ki;
    }

    float kd() const {
        return _kd;
    }

    float kf() const {
        return _kf;
    }

    float kv() const {
        return _kf;
    }

    // 支持链式调用的赋值函数
    PidParamPack &kp(float val) {
        _kp = val;
        return *this;
    }

    PidParamPack &ki(float val) {
        _ki = val;
        return *this;
    }

    PidParamPack &kd(float val) {
        _kd = val;
        return *this;
    }

    // 提供一个别名
    PidParamPack &kv(float val) {
        _kf = val;
        return *this;
    }

    PidParamPack &kf(float val) {
        _kf = val;
        return *this;
    }
};


namespace _hide {

    template <typename T>
    struct string_to {
        static T from(QStringView s, bool *ok = nullptr) {
            *ok = false;
            return {0};
        }
    };


    template <>
    struct string_to<int> {
        static int from(QStringView s, bool *ok = nullptr) {
            return s.toInt(ok);
        }
    };


    template <>
    struct string_to<uint> {
        static uint from(QStringView s, bool *ok = nullptr) {
            return s.toUInt(ok);
        }
    };


    template <>
    struct string_to<float> {
        static float from(QStringView s, bool *ok = nullptr) {
            return s.toFloat(ok);
        }
    };


    template <>
    struct string_to<double> {
        static float from(QStringView s, bool *ok = nullptr) {
            return s.toDouble(ok);
        }
    };


    template <typename T>
    inline bool convert_param_and_check(QStringView text,
                                        const QString &name,
                                        T min,
                                        T max,
                                        T &out,
                                        QStringList &error_result_list,
                                        bool ignore_blank) {
        do {
            if (!ignore_blank && text.isEmpty()) {
                break;
            }

            bool flag = true;
            T num = string_to<T>::from(text, &flag);

            if (!flag || (num < min) || (num > max)) {
                break;
            }

            out = num;
            return true;
        } while (0);

        error_result_list.append(name);
        return false;
    }


    template <typename T>
    inline bool convert_param_list_and_check(QStringView text,
                                             const QRegularExpression *seq,
                                             const QString &name,
                                             T min,
                                             T max,
                                             QList<T> &out,
                                             unsigned expected_len,
                                             QStringList &error_result_list,
                                             bool ignore_blank) {
        assert(seq != nullptr);
        unsigned len = out.size();

        do {
            if (text.isEmpty()) {
                if (ignore_blank) {
                    return true;
                }
                else {
                    break;
                }
            }

            auto l = text.split(*seq, Qt::SkipEmptyParts);

            for (QStringView s : l) {
                bool flag = true;
                T num = string_to<T>::from(s, &flag);

                if (!flag || (num < min) || (num > max)) {
                    break;
                }

                out.push_back(num);
            }

            // 如果解析结果的数量对不上期望的数量，返回错误
            // out 列表初始长度不一定是0，所以要用增量对比
            len = out.size() - len;
            if ((!ignore_blank && len == 0) || (expected_len != 0 && len != expected_len)) {
                break;
            }

            return true;
        } while (0);

        error_result_list.append(name);
        return false;
    }


    union Number {
        long i64;
        ulong u64;
        int i32;
        uint u32;

        double f64;
        float f32;
    };


    class FormFieldChecker {
       private:
        QStringList _error_result_list;
        Number _min, _max;

        const QRegularExpression *_seq = nullptr;

        unsigned _param_list_len = 0;
        unsigned _last_param_list_len = 0;

        bool _flag = true;
        bool _ignore_blank = true;
        bool _reading_param_group = false;

       public:
        // FormFieldChecker() {}

        void set_int_limit(int min, int max) {
            _min.i32 = min;
            _max.i32 = max;
        }

        void set_float_limit(float min, float max) {
            _min.f32 = min;
            _max.f32 = max;
        }

        void set_double_limit(double min, double max) {
            _min.f64 = min;
            _max.f64 = max;
        }

        void set_list_delimiter(const QRegularExpression *seq) {
            _seq = seq;
            _seq->optimize();
        }

        int read_int(const QLineEdit *field, const QString &name) {
            auto text = field->text();
            int num;
            _flag = convert_param_and_check<int>(text, name, _min.i32, _max.i32, num,
                                                 _error_result_list,
                                                 _ignore_blank);
            return num;
        }

        float read_float(const QLineEdit *field, const QString &name) {
            auto text = field->text();
            float num;
            _flag = convert_param_and_check<float>(text, name, _min.f32, _max.f32, num,
                                                   _error_result_list,
                                                   _ignore_blank);
            return num;
        }

        QList<int> read_int_list(const QLineEdit *field, const QString &name) {
            auto text = field->text();
            QList<int> result;
            _flag = convert_param_list_and_check<int>(text, _seq, name, _min.i32, _max.i32, result,
                                                      _param_list_len,
                                                      _error_result_list,
                                                      _ignore_blank);

            if (_reading_param_group && _param_list_len == 0) {
                _param_list_len = result.size();
            }

            return result;
        }

        QList<float> read_float_list(const QLineEdit *field, const QString &name) {
            auto text = field->text();
            QList<float> result;
            _flag = convert_param_list_and_check<float>(text, _seq, name, _min.i32, _max.i32, result,
                                                        _param_list_len,
                                                        _error_result_list,
                                                        _ignore_blank);

            if (_reading_param_group && _param_list_len == 0) {
                _param_list_len = result.size();
            }

            return result;
        }

        bool ok() const {
            return _flag;
        }

        bool no_error() const {
            return _error_result_list.isEmpty();
        }

        const QStringList &get_error_param_list() const {
            return _error_result_list;
        }

        void begin_param_list_group(bool ignore_blank = true, unsigned expected_len = 0) {
            _reading_param_group = true;
            _ignore_blank = ignore_blank;
            _param_list_len = expected_len;
        }

        void end_param_list_group() {
            _reading_param_group = false;
            _param_list_len = 0;
        }
    };

};  // namespace _hide


class LipidExec {
   private:
    lipid::BicycleThermalPid _pid2;
    lipid::HeatSim<10, 200, 10> _sim{80, 15, 20};  // TODO: 模拟参数可调
    lipid::HeatSimSensor<decltype(_sim)> _sensor{_sim};
    lipid::HeatSimActuator<decltype(_sim)> _actuator{_sim};

    PidParamPack _gc0_param;
    PidParamPack _gc1_param;

    unsigned _run_count = 1;
    QList<float> _sp_list;

    std::unique_ptr<QLineSeries> _sensor_line = nullptr;
    // QLineSeries _temp_line;
    std::unique_ptr<QLineSeries> _power_line = nullptr;
    std::unique_ptr<QLineSeries> _sp_line = nullptr;
    std::unique_ptr<QLineSeries> _error_line = nullptr;


   public:
    LipidExec() {
        _pid2.set_as_v_feedback(&_sensor, &_actuator);
        _sim.add_point(80, 5, 2);
        _pid2.gc0().set_output_limit(-255, 255);
        _pid2.gc1().set_output_limit(-255, 255);

        reset_record();
    }

    PidParamPack &gc0() {
        return _gc0_param;
    }

    PidParamPack &gc1() {
        return _gc1_param;
    }

    void set_sample_time(float gc1_sample_time, unsigned gc0_times) {
        _pid2.tune_gc0(_gc0_param.kp(), _gc0_param.ki(), _gc0_param.kd());
        _pid2.tune_gc1(_gc1_param.kp(), _gc1_param.ki(), _gc1_param.kd(), _gc1_param.kv());
        _pid2.set_sample_time(gc1_sample_time, gc0_times);
    }

    void sim_setup(unsigned run_count, const QList<float> &&set_point_list) {
        if (run_count < 1) {
            _run_count = 1;
        }
        else {
            _run_count = run_count;
        }

        _sp_list = set_point_list;
    }

    void reset_record() {
        _sensor_line.reset(new QLineSeries{});
        _sensor_line->setName(QStringLiteral(u"传感器"));

        _power_line.reset(new QLineSeries{});
        _power_line->setName(QStringLiteral(u"功率"));

        _sp_line.reset(new QLineSeries{});
        _sp_line->setName(QStringLiteral(u"目标"));

        _error_line.reset(new QLineSeries{});
        _error_line->setName(QStringLiteral(u"误差"));
    }

    void run_sim() {
        float power = 0;
        float out = 0;

        unsigned t = 0;
        for (float sp : _sp_list) {
            for (int i = 0; i < _run_count; ++i) {
                power = _pid2.count(sp);

                auto add_point = [t](QLineSeries &l, float val) {
                    l.append(t, val);
                };

                add_point(*_power_line, power);
                add_point(*_sp_line, sp);
                add_point(*_sensor_line, _pid2.get_main_feedback());
                add_point(*_error_line, sp - _pid2.get_main_feedback());

                ++t;
            }
        }
    }

    void present_to_chart(QChart& chart) {
        //chart.removeAllSeries();
        chart.addSeries(_sp_line.release());
        chart.addSeries(_power_line.release());
        chart.addSeries(_sensor_line.release());
        chart.addSeries(_error_line.release());

        reset_record();
    }
};


class HeatSimMain : public QMainWindow {
    Q_OBJECT

   private:
    Ui::HeatSimMainClass _ui;
    std::unique_ptr<QChart> _chart;

    QSettings _settings;

    LipidExec _le;

   public slots:
    void runLipid() {
        loadParam();

        _le.run_sim();
        _chart->removeAllSeries();
        _le.present_to_chart(*_chart);
        _chart->createDefaultAxes();
    }

    void showAboutQt() {
        QMessageBox::aboutQt(this);
    }

   public:
    constexpr static QStringView ORG_NAME{u"Qt6 Application"};
    constexpr static QStringView APP_NAME{u"VS QT HeatSim"};
    constexpr static QStringView GROUP_NAME{u"Main Window"};

    explicit HeatSimMain(QWidget *parent = nullptr) :
        QMainWindow(parent),
        _settings{QString{ORG_NAME.data()}, QString{GROUP_NAME.data()}},
        _chart(new QChart{}) {
        //
        _ui.setupUi(this);
        // 只允许输入正整数的输入框
        QLineEdit *intEditList[] = {_ui.gc0SampleTimeTimesEdit,
                                    _ui.runCountEdit};
        // 只允许输入正浮点数的输入框
        QLineEdit *positiveFloatEditList[] = {_ui.gc1SampleTimeEdit};
        // 只允许正、负浮点数的输入框
        QLineEdit *floatEditList[] = {_ui.gc0KpEdit,
                                      _ui.gc0KiEdit,
                                      _ui.gc0KdEdit,
                                      _ui.gc1KpEdit,
                                      _ui.gc1KiEdit,
                                      _ui.gc1KdEdit,
                                      _ui.gc1KvEdit};
        // 只允许正、负浮点数和逗号的输入框
        QLineEdit *floatAndCommaEditList[] = {_ui.setPointEdit};

        // TODO: 添加输入验证器

        //_ui.chartView->setRubberBand(QChartView::RectangleRubberBand);
        connect(_ui.chartResetBtn, SIGNAL(clicked()), _ui.chartView, SLOT(resetChart()));
        connect(_ui.horizontalZoomSwitch, SIGNAL(clicked(bool)), _ui.chartView, SLOT(enableHorizontalZoom(bool)));
        connect(_ui.verticalZoomSwitch, SIGNAL(clicked(bool)), _ui.chartView, SLOT(enableVerticalZoom(bool)));
        connect(_ui.runBtn, SIGNAL(clicked()), this, SLOT(runLipid()));
        connect(_ui.actionAboutQt, SIGNAL(triggered()), this, SLOT(showAboutQt()));

        // new 一个 QLineSeries实例
        QLineSeries *series = new QLineSeries{this};
        series->setName("VSQT HEAT SIM");
        // 添加实验数据,可以用 append 方法 或者  >> 操作符
        series->append(0, 2);
        series->append(QPointF(2, 6));
        series->append(3, 8);
        series->append(7, 9);
        series->append(11, 3);
        _chart->addSeries(series);
        _chart->createDefaultAxes();

        _ui.chartView->setChart(_chart.get());

        readState();
    }

    ~HeatSimMain() {}

   private:
    void warnParam(QStringList param_name_list) {
        auto msg = QString("检查以下参数，可能存在格式错误或列表长度不一致：\n");
        msg += param_name_list.join("\n");
        QMessageBox::warning(this, QString{"参数错误"}, msg);
    }

    void warnParam(QStringView param_name) {
        auto msg = QString("检查参数，可能存在格式错误或列表长度不一致：%1").arg(param_name);
        QMessageBox::warning(this, QString{"参数错误"}, msg);
    }


    bool loadParam() {
        // 从表单加载输入参数
        bool flag = true;

        _hide::FormFieldChecker chk;

        chk.set_int_limit(1, 1000);
        int gc0_sample_time_times = chk.read_int(_ui.gc0SampleTimeTimesEdit, QStringLiteral(u"Gc0 / 采样倍率"));

        chk.set_int_limit(1, 100000);
        int run_count = chk.read_int(_ui.runCountEdit, QStringLiteral(u"迭代次数"));

        chk.set_float_limit(0.0f, 1e6f);
        float gc0_kp = chk.read_float(_ui.gc0KpEdit, QStringLiteral(u"Gc0 / Kp"));
        float gc0_ki = chk.read_float(_ui.gc0KiEdit, QStringLiteral(u"Gc0 / Ki"));
        float gc0_kd = chk.read_float(_ui.gc0KdEdit, QStringLiteral(u"Gc0 / Kd"));

        float gc1_sample_time = chk.read_float(_ui.gc1SampleTimeEdit, QStringLiteral(u"Gc1 / 采样时间"));
        float gc1_kp = chk.read_float(_ui.gc1KpEdit, QStringLiteral(u"Gc1 / Kp"));
        float gc1_ki = chk.read_float(_ui.gc1KiEdit, QStringLiteral(u"Gc1 / Ki"));
        float gc1_kd = chk.read_float(_ui.gc1KdEdit, QStringLiteral(u"Gc1 / Kd"));
        float gc1_kv = chk.read_float(_ui.gc1KvEdit, QStringLiteral(u"Gc1 / Kv"));

        QRegularExpression seq{"[,，]"};
        chk.set_list_delimiter(&seq);

        chk.set_float_limit(-10000.0f, 10000.0f);
        QList<float> set_point_list = chk.read_float_list(_ui.setPointEdit, QStringLiteral(u"调定点"));

        if (!chk.no_error()) {
            warnParam(chk.get_error_param_list());
            return false;
        }

        _le.gc0().kp(gc0_kp).ki(gc0_ki).kd(gc0_kd);
        _le.gc1().kp(gc1_kp).ki(gc1_ki).kd(gc1_kd);
        _le.set_sample_time(gc1_sample_time, gc0_sample_time_times);

        _le.sim_setup(run_count, std::move(set_point_list));

        return true;
    }

    SettingsScope openSettingsGroup() {
        return {_settings, GROUP_NAME};
    }

    void writeState() {
        auto ss = openSettingsGroup();
        ss.settings.setValue("geometry", saveGeometry());
        ss.settings.setValue("state", saveState());
    }

    void readState() {
        auto ss = openSettingsGroup();

        const auto geometry = ss.settings.value("geometry", QByteArray()).toByteArray();
        if (!geometry.isEmpty())
            restoreGeometry(geometry);

        const auto state
            = ss.settings.value("state", QByteArray()).toByteArray();
        if (!state.isEmpty())
            restoreState(state);
    }

    void clearState() {
        auto ss = openSettingsGroup();
        ss.settings.clear();
    }

    virtual void closeEvent(QCloseEvent *event) override {
        this->writeState();
        event->accept();
    }
};
